import json
import math
import random
import re
import time
from datetime import datetime, timedelta

from selenium.common import NoSuchElementException
from selenium.webdriver import ActionChains, Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver

from libs.account import Account
from libs.action import WebDriverAction
from libs.db import DB
from libs.image import ImageTool
from libs.log import Log
from libs.task_helper import TaskHelper
from libs.window import AdsWindow


def _get_snapshot_all():
    return DB().get_snapshots()


def _get_snapshot(user_id):
    snapshot_all = _get_snapshot_all()
    return snapshot_all.get(str(user_id), {})


class Game:
    def __init__(self, driver: WebDriver, user_id):
        self.driver = driver
        self.image_tool = ImageTool(driver)
        self.action = WebDriverAction(driver)
        self.log = Log(user_id)
        self.user_id = user_id
        self.backpack = None
        self.full_backpack = None
        self.wines = {
            "Berry Blast": 100,
            "Blue Grumpkin Wine": 215,
            "Grainshine": 105,
            "Popberry Wine": 95
        }

    def in_game(self):
        try:
            self.driver.find_element(By.CLASS_NAME, "Hud_outside__zzIGQ")
            return True
        except:
            return False

    def enter_game(self, select_world=True, try_times=2):
        action = WebDriverAction(self.driver)
        action.escape()
        action.escape()
        action.handle_dialog()
        if self.in_game():
            return
        enter_success = True
        need_login = False
        for loop in range(try_times):
            enter_success = True
            need_login = False
            if self.in_game():
                break
            try:
                url = "https://play.pixels.xyz/"
                # 设置页面加载的超时时间
                self.driver.get(url)

                time.sleep(1)
                login_panel = action.find_element(By.CLASS_NAME, 'Intro_walletGateWrapper__jFySU', 'LoginPanel', 3)
                if login_panel is not None and login_panel.value_of_css_property('display') == 'flex':
                    need_login = True
                    
                if select_world:
                    # 选择服务器
                    action.find_and_click_by_class('Intro_smalllink__nJ3cG', 'SelectWorld', 3)
                    world_items = action.find_elements(By.CLASS_NAME, 'Intro_worldItem__2mnq7', 'WorldList', 3)
                    if world_items is None:
                        continue
                    total = len(world_items)
                    if total > 100:
                        random_idx = random.randint(100, total - 1)
                    else:
                        random_idx = random.randint(0, total - 1)
                    print(f"select world {random_idx}", flush=True)
                    word_item = world_items[random_idx]
                    ActionChains(self.driver).scroll_to_element(word_item)
                    time.sleep(0.5)
                    word_item.click()
                else:
                    action.find_and_click_by_class('Intro_startbutton__QtxEz', 'StartButton')
                action.wait_page_loaded()
            except Exception as e:
                print('enter failed')
                print(e)
                enter_success = False
                continue

        if need_login:
            Log('notify').info(f'[{self.user_id}]: need login')
            TaskHelper(self.user_id).add_task('login.py', delay_seconds=0, ignore_running=False, priority=99)
        if not enter_success:
            AdsWindow(self.user_id).close()
            raise Exception('enter game failed')

        action.escape()
        action.escape()
        action.handle_dialog()

    def snapshot(self, user_id, operation, include_skills=True):
        account = Account().get(self.user_id)
        last_update = account.get('last_update')
        if last_update is None or (datetime.now() - datetime.strptime(last_update, '%Y-%m-%d %H:%M:%S')) > timedelta(hours=24):
            # 查看用户信息
            self.driver.find_elements(By.CLASS_NAME, "Hud_outside__zzIGQ")[2].click()
            time.sleep(1)
            self.driver.find_element(By.XPATH, "//button[text()='Player Statistics']").click()
            time.sleep(1)
            name_item = self.driver.find_element(By.XPATH, "//span[text()='Username']/following-sibling::span")
            vip_item = self.driver.find_element(By.XPATH, "//span[text()='VIP']/following-sibling::span")
            name = name_item.text
            vip_text = vip_item.text
            is_vip = vip_text != 'No'
            vip_expire_date = vip_text.replace('Expires ', '')
            Account().save_user_info(user_id, name, is_vip, vip_expire_date)
            action = WebDriverAction(self.driver)
            action.escape()
            action.escape()
            time.sleep(1)

        result = {
            'energy': self.get_energy(),
            'coin': self.get_coin(),
            'p': self.get_p(),
        }
        Log(user_id).info(operation + ": " + json.dumps(result))
        if include_skills:
            try:
                skill_total, skills = self.get_skills()
                result['skill_sum'] = skill_total
                result['skills'] = skills
            except:
                pass
        DB().save_snapshot(self.user_id, result)
        return result

    def get_skills(self):
        self.driver.find_elements(By.CLASS_NAME, "Hud_outside__zzIGQ")[1].click()
        time.sleep(0.5)
        skill_items = self.driver.find_elements(By.CLASS_NAME, 'Skills_skillContent__Pin9G')
        skills = {}
        skill_total = 0
        skill_names = ['farm', 'forestry', 'cooking', 'mining', 'wood', 'metal', 'stone', 'animal', 'business',
                       'explore']
        index = 0
        for skill_item in skill_items:
            if index >= len(skill_names):
                break
            skill_name = skill_names[index]
            skill_level = int(skill_item.find_element(By.CLASS_NAME, "Skills_skillPoints__xvU6j").text)
            skill_progress_style = skill_item.find_element(By.CLASS_NAME,
                                                           'Skills_skillFilledBar__Ry4WY').get_attribute('style')
            process_match = re.search(r'width: (.*)%', skill_progress_style)
            skill_process = 0
            if process_match:
                skill_process = math.floor(float(process_match.group(1)))
            if skill_level > 0 or skill_process > 0:
                skill_total += skill_level
                skills[skill_name] = round(skill_level + skill_process / 100, 2)
            index += 1

        time.sleep(0.5)
        self.driver.find_elements(By.CLASS_NAME, "Hud_outside__zzIGQ")[1].click()
        return skill_total, skills

    def get_energy(self):
        energy_elem = self.driver.find_element(By.XPATH, '//div[contains(@class,"Hud_energy__67nU9")]//div//span[contains(@class,"commons_coinBalance__d9sah")]')
        energy = energy_elem.text
        energy = re.sub(r'[^\d,.]', '', energy)
        if energy == '1.000':
            return 1000
        if float(energy.replace(',', '')) <= 1000:
            return float(energy.replace(',', ''))
        else:
            return float(energy.replace(',', '.'))

    def get_coin(self):
        coin_elem = self.driver.find_element(By.XPATH, '//div[contains(@class,"Hud_berry__6A2FS")]//div//span[contains(@class,"commons_coinBalance__d9sah")]')
        coin_text = re.sub(r'[^\d,.]', '', coin_elem.text)
        coin = int(coin_text.replace(',', '').replace('.', ''))
        return coin

    def get_p(self):
        p_elem = self.driver.find_element(By.XPATH, '//div[contains(@class,"Hud_pixelcoin__EUbKE")]//div//span[contains(@class,"commons_coinBalance__d9sah")]')
        p = float(p_elem.text.replace(',', '.'))
        return round(p, 1)

    def collect_mail(self):
        mail_button = self.driver.find_element(By.XPATH, "//button[img[@aria-label='Mail']]")
        mail_button.click()
        time.sleep(3)
        for _ in range(10):
            try:
                mail_list = self.driver.find_elements(By.CLASS_NAME, 'MailBox_mailboxItemWrapper__tcY_A')
                if len(mail_list) == 0:
                    break
                mail_list[0].find_element(
                    By.XPATH,
                    "//div[contains(@class, 'MailBox_collectButtonsContainer__DRAE_')]//button[1]"
                ).click()
                time.sleep(5)
            except NoSuchElementException:
                break
        self.action.escape()
        self.action.escape()

    def open_backpack(self):
        open_loc = self.image_tool.find_target('backpack_opened.png')
        if open_loc is None:
            body = self.driver.find_element(By.TAG_NAME, 'html')
            body.send_keys('b')
            time.sleep(1)

    def close_backpack(self):
        open_loc = self.image_tool.find_target('backpack_opened.png')
        if open_loc is not None:
            body = self.driver.find_element(By.TAG_NAME, 'html')
            body.send_keys('b')
            time.sleep(1)

    def get_backpack(self):
        data = {}
        full_data = {}
        ac = ActionChains(self.driver)
        self.open_backpack()
        cell_items = self.driver.find_elements(By.CLASS_NAME, 'Hud_item__YGtIC')
        for index, cell_elem in enumerate(cell_items):
            try:
                qty_elem = cell_elem.find_element(By.CLASS_NAME, 'Hud_quantity__V_YWQ')
                if qty_elem.text == '':
                    qty = 1
                else:
                    qty = int(qty_elem.text.replace('x', ''))
            except NoSuchElementException:
                full_data[index + 1] = {}
                continue
            ac.move_to_element(cell_elem).perform()
            try:
                item_name = None
                for _ in range(3):
                    try:
                        hover_title_elem = self.driver.find_element(By.XPATH, "//span[@class='ItemStyles_tooltipTitle____kIs']")
                        item_name = hover_title_elem.text
                        break
                    except Exception as e:
                        pass
                if item_name is None:
                    raise Exception('backpack item_name get failed')
                full_data[index + 1] = {'name': item_name, 'qty': qty}
                data[index + 1] = {'name': item_name, 'qty': qty}
            except NoSuchElementException:
                continue
        ac.move_by_offset(150, 0).perform()
        self.close_backpack()
        if data == {}:
            raise Exception('get_backpack_failed')
        self.backpack = data
        self.full_backpack = full_data

        DB().save_snapshot(self.user_id, {"backpack": data})
        return data
    def update_backpack(self, indexes):
        ac = ActionChains(self.driver)
        self.open_backpack()
        cell_items = self.driver.find_elements(By.CLASS_NAME, 'Hud_item__YGtIC')
        for index, cell_elem in enumerate(cell_items):
            if (index + 1) not in indexes:
                continue
            try:
                qty_elem = cell_elem.find_element(By.CLASS_NAME, 'Hud_quantity__V_YWQ')
                if qty_elem.text == '':
                    qty = 1
                else:
                    qty = int(qty_elem.text.replace('x', ''))
            except NoSuchElementException:
                self.backpack[index + 1] = {}
                self.full_backpack[index + 1] = {}
                continue
            ac.move_to_element(cell_elem).perform()
            try:
                item_name = None
                for _ in range(3):
                    try:
                        hover_title_elem = self.driver.find_element(By.XPATH, "//span[@class='ItemStyles_tooltipTitle____kIs']")
                        item_name = hover_title_elem.text
                        break
                    except Exception as e:
                        pass
                if item_name is None:
                    raise Exception('backpack item_name get failed')
                self.backpack[index + 1] = {'name': item_name, 'qty': qty}
                self.full_backpack[index + 1] = {'name': item_name, 'qty': qty}
            except NoSuchElementException:
                continue
        self.close_backpack()

    def get_backpack_qty(self, item_name):
        if self.backpack is None:
            self.backpack = self.get_backpack()
        qty = 0
        for index in self.backpack:
            item = self.backpack.get(index)
            if item.get('name') == item_name:
                qty += item.get('qty')
        return qty

    def get_backpack_index_and_qty(self, item_name):
        if self.backpack is None:
            self.backpack = self.get_backpack()

        for index in self.backpack:
            bp_item = self.backpack.get(index)
            if item_name == bp_item.get('name'):
                return index, bp_item.get('qty')
        return None, 0

    def get_backpack_space(self):
        if self.full_backpack is None:
            self.get_backpack()
        space = 0
        for index in self.full_backpack:
            item = self.full_backpack.get(index)
            qty = item.get('qty', 0)
            if qty <= 0:
                space += 1
        return space

    def use_backpack_item(self, index):
        index = int(index)
        body = self.driver.find_element(By.TAG_NAME, 'html')
        body.send_keys(Keys.ESCAPE)
        if index > 6:
            self.open_backpack()
            cell_items = self.driver.find_elements(By.CLASS_NAME, 'Hud_item__YGtIC')
            cell_items[index - 1].click()
            self.close_backpack()
        else:
            body.send_keys(str(index))

    def has_wine(self):
        for wine_name in self.wines:
            idx, wine_qty = self.get_backpack_index_and_qty(wine_name)
            if wine_qty > 0:
                return True
        return False

    def is_total_wine_enough(self, target=100):
        total = 0
        for wine_name in self.wines:
            idx, wine_qty = self.get_backpack_index_and_qty(wine_name)
            if wine_qty > 0:
                total += wine_qty
        return total >= target

    def is_min_wine_enough(self, target=20):
        min_qty = 0
        for wine_name in self.wines:
            idx, wine_qty = self.get_backpack_index_and_qty(wine_name)
            if wine_qty < min_qty:
                min_qty = wine_qty
        return min_qty >= target

    def drink_wine(self, energy_target, click_x, click_y):
        self.log.info(f"drink energy start: energy={self.get_energy()}, need {energy_target}")
        wine_name = None
        wine_qty = 0
        wine_energy = 0
        for name in self.wines:
            qty = self.get_backpack_qty(name)
            if qty > wine_qty:
                wine_name = name
                wine_qty = qty
                wine_energy = self.wines.get(name)
        if wine_qty == 0:
            return
        wine_index, _ = self.get_backpack_index_and_qty(wine_name)
        self.use_backpack_item(wine_index)
        before_drink_energy = self.get_energy()
        no_effect_count = 0
        for _ in range(20):
            if energy_target - self.get_energy() < wine_energy:
                self.action.escape()
                break
            # 点击角色喝酒
            self.action.click_by_offset(click_x, click_y)
            after_drink_energy = self.get_energy()
            if after_drink_energy - before_drink_energy > 10:
                before_drink_energy = after_drink_energy
                continue
            else:
                # 体力没增加
                no_effect_count += 1
                if no_effect_count > 2:
                    self.action.escape()
                    break
                continue
        self.log.debug(f"drink energy end: energy={self.get_energy()}")
        time.sleep(1)
        self.action.escape()

    def chest_scan(self):
        chest_items = {}
        for _ in range(3):
            try:
                chest_items = {}
                ac = ActionChains(self.driver)
                ac.reset_actions()
                cell_items = self.driver.find_elements(By.CLASS_NAME, 'InventoryWindow_inventoryItem__z7YhV')
                for index, cell_elem in enumerate(cell_items):
                    try:
                        qty_elem = cell_elem.find_element(By.CLASS_NAME, 'InventoryWindow_itemQuantity__SNjQU')
                        if qty_elem.text == '':
                            qty = 1
                        else:
                            qty = int(qty_elem.text.replace('x', ''))
                    except NoSuchElementException:
                        continue
                    ac.move_to_element(cell_elem).perform()
                    try:
                        hover_title_elem = self.driver.find_element(By.XPATH,
                                                                    "//span[@class='ItemStyles_tooltipTitle____kIs']")
                        item_name = hover_title_elem.text
                        chest_items[index] = {
                            'name': item_name,
                            'qty': qty
                        }
                    except NoSuchElementException:
                        continue
                time.sleep(1)
                break
            except Exception as e:
                print(e)
                pass
        return chest_items

    def chest_in(self, x, y, chest_items, keep=None, min_qty=0):
        if keep is None:
            keep = {}
        in_count = 0
        for _ in range(3):
            try:
                in_count = 0
                ac = ActionChains(self.driver)
                self.open_backpack()
                time.sleep(1)
                ac.reset_actions()
                ac.move_by_offset(x, y).click().perform()
                cell_items = self.driver.find_elements(By.CLASS_NAME, 'Hud_item__YGtIC')
                total_keep = 0
                for index, cell_elem in enumerate(cell_items):
                    try:
                        qty_elem = cell_elem.find_element(By.CLASS_NAME, 'Hud_quantity__V_YWQ')
                        if qty_elem.text == '':
                            qty = 1
                        else:
                            qty = int(qty_elem.text.replace('x', ''))
                    except NoSuchElementException:
                        continue
                    if qty < min_qty:
                        continue
                    ac.move_to_element(cell_elem).perform()
                    try:
                        hover_title_elem = self.driver.find_element(By.XPATH,
                                                                    "//span[@class='ItemStyles_tooltipTitle____kIs']")
                        item_name = hover_title_elem.text
                        if item_name in chest_items:
                            keep_qty = keep.get(item_name, 0)
                            if keep_qty - total_keep > 0:
                                total_keep += qty
                                continue
                            else:
                                ac.context_click().perform()
                                time.sleep(1)
                                ac.context_click().perform()
                                time.sleep(1)
                                in_count += 1
                    except NoSuchElementException:
                        continue
                time.sleep(1)
                self.action.escape()
                self.action.escape()
                self.close_backpack()
                break
            except Exception as e:
                print(e)
                pass
        return in_count

    def chest_out(self, x, y, chest_items, min_qty=0):
        for _ in range(3):
            try:
                ac = ActionChains(self.driver)
                self.open_backpack()
                time.sleep(1)
                self.action.click_by_offset(x, y)
                time.sleep(1)
                cell_items = self.driver.find_elements(By.CLASS_NAME, 'InventoryWindow_inventoryItem__z7YhV')
                for index, cell_elem in enumerate(cell_items):
                    try:
                        qty_elem = cell_elem.find_element(By.CLASS_NAME, 'InventoryWindow_itemQuantity__SNjQU')
                        if qty_elem.text == '':
                            qty = 1
                        else:
                            qty = int(qty_elem.text.replace('x', ''))
                    except NoSuchElementException:
                        continue
                    if qty < min_qty:
                        continue
                    ac.move_to_element(cell_elem).perform()
                    try:
                        hover_title_elem = self.driver.find_element(By.XPATH,
                                                                    "//span[@class='ItemStyles_tooltipTitle____kIs']")
                        item_name = hover_title_elem.text
                        if item_name in chest_items:
                            ac.context_click().perform()
                            time.sleep(1)
                            ac.context_click().perform()
                            time.sleep(1)
                    except NoSuchElementException:
                        continue
                time.sleep(1)
                self.action.escape()
                self.action.escape()
                self.close_backpack()
                break
            except Exception as e:
                print(e)
                pass